#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/limits.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>

#define MODE 			(S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
#define BUF_SIZE		4096
#define OPTIONS_SIZE 	8

static char options[OPTIONS_SIZE] = { 0 };

static inline void err_fatal(const char* msg) {
	perror(msg);
	exit(-1);
}

static int is_member(char ch) {
	for (int i = 0; i < OPTIONS_SIZE; i++) {
		if (options[i] == ch) {
			return 1;
		}
	}
	return 0;
}

/************************************* 命令选项 *************************************/

static inline int opt_override() {
	return is_member('o');
}

int main(int argc, char* argv[]) {
	if (argc < 3) {
		printf("usage: %s [options] src-file dst-file\n", argv[0]);
		exit(-1);
	}

	const char* srcpath = NULL;
	const char* dstpath = NULL;
	int n_opts = 0;

	// 解析命令行参数
	for (int i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			if (OPTIONS_SIZE < n_opts + strlen(argv[i]) -1) {
				fprintf(stderr, "%s\n", "too much options");
				exit(-1);
			}

			strcpy(options + n_opts, argv[i] + 1);
			n_opts += strlen(argv[i]) - 1;
		}
		else if (!srcpath) {
			srcpath = argv[i];
		}
		else if (!dstpath) {
			dstpath = argv[i];
		}
		else {
			fprintf(stderr, "%s\n", "multiple targets are provided");
			exit(-1);
		}
	}

	if (!srcpath || !dstpath) {
		fprintf(stderr, "%s\n", "source path or destination path is missing");
		exit(-1);
	}

	char tmp_dstpath[PATH_MAX];
	struct stat sb;
	int ret, srcfd, dstfd, dstflags = O_WRONLY;
	char option;

	// 查看源文件是否存在
	ret = stat(srcpath, &sb);
	if (ret == -1) {
		err_fatal(srcpath);
	}

	// 源文件是目录
	if (S_ISDIR(sb.st_mode)) {
		fprintf(stderr, "%s is a directory\n", srcpath);
		exit(-1);
	}

	// 查看目标路径是否存在
	ret = stat(dstpath, &sb);
	if (ret == -1) {					// 目标不存在
		if (errno != ENOENT) {
			err_fatal(dstpath);
		}
		
		dstflags |= O_CREAT | O_EXCL;	// 假设其为文件
	}
	else {								// 目标存在
		if (S_ISDIR(sb.st_mode)) {		// 为目录
			int dstlen = strlen(dstpath);
			int srclen = strlen(srcpath);
			int i;

			strcpy(tmp_dstpath, dstpath);
			if (tmp_dstpath[dstlen - 1] != '/') {
				tmp_dstpath[dstlen] = '/';
				dstlen++;
			}

			for (i = srclen - 1; i >= 0; i--) {
				if (srcpath[i] == '/') {
					break;
				}
			}

			strcpy(tmp_dstpath + dstlen, srcpath + i + 1);
			dstpath = tmp_dstpath;
		}

		ret = stat(dstpath, &sb);
		if (ret == -1) {
			if (errno != ENOENT) {
				err_fatal(dstpath);
			}
			
			dstflags |= O_CREAT | O_EXCL;
		}
		else if (opt_override()) {
			dstflags |= O_TRUNC;
		}
		else {
			fprintf(stdout, "%s already exists[Override/Merge/Cancel]:", dstpath);
			scanf("%c", &option);

			switch (option) {
			case 'O':
			case 'o':
				dstflags |= O_TRUNC;
				break;

			case 'M':
			case 'm':
				dstflags |= O_APPEND;
				break;

			case 'C':
			case 'c':
			default:
				exit(0);
			}
		}
	}

	if (dstflags & O_CREAT) {
		dstfd = open(dstpath, dstflags, MODE);
	}
	else {
		dstfd = open(dstpath, dstflags);
	}
	if (dstfd == -1) {
		err_fatal(dstpath);
	}

	srcfd = open(srcpath, O_RDONLY);
	if (srcfd == -1) {
		err_fatal(srcpath);
	}

	char buf[BUF_SIZE];
	ssize_t nread, nwrite;

	while ((nread = read(srcfd, buf, BUF_SIZE)) > 0) {
		nwrite = write(dstfd, buf, nread);
		if (nwrite < nread) {
			fprintf(stderr, "%s\n", "nwrite < nread");
			exit(-1);
		}
	}
	if (nread == -1) {
		err_fatal("read");
	}

	close(srcfd);
	close(dstfd);

	return 0;
}

